package com.cjx.stars.ws;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.cjx.stars.config.HttpSessionConfig;
import com.cjx.stars.pojo.request.MessageRequest;
import com.cjx.stars.pojo.table.Chat;
import com.cjx.stars.pojo.table.Team;
import com.cjx.stars.pojo.table.User;
import com.cjx.stars.pojo.vo.ChatMessageVO;
import com.cjx.stars.pojo.vo.WebSocketVO;
import com.cjx.stars.service.ChatService;
import com.cjx.stars.service.TeamService;
import com.cjx.stars.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import static com.cjx.stars.constant.ChatConstant.*;
import static com.cjx.stars.constant.UserConstant.USER_LOGIN_STATE;

/**
 * @author cjx
 * @description WebSocket 服务类
 * @createDate 2024/4/13 15:04
 **/
@Component
@Slf4j
@ServerEndpoint(value = "/websocket/{userId}/{teamId}", configurator = HttpSessionConfig.class)
public class ChatEndpoint {

    /**
     * http会话
     */
    private HttpSession httpSession;
    /**
     * 当前信息
     */
    private Session session;
    /**
     * 当前在线的用户集合
     */
    private static final Map<String, Session> ONLINE_USERS = new ConcurrentHashMap<>();
    /**
     * 线程安全的无序的集合
     */
    private static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();
    /**
     * 保存队伍的连接信息
     */
    private static final Map<String, ConcurrentHashMap<String, ChatEndpoint>> ROOMS = new HashMap<>();

    private static UserService userService;

    private static TeamService teamService;

    private static ChatService chatService;

    private static final String UNDEFINED = "undefined";
    private static final String NAN = "NaN";
    private static final String PING = "PING";

    /**
     * 载入用户服务类
     *
     * @param userService 用户服务
     */
    @Resource
    public void setUserService(UserService userService) {
        ChatEndpoint.userService = userService;
    }

    /**
     * 载入聊天服务服务类
     *
     * @param chatService 聊天服务
     */
    @Resource
    public void setChatService(ChatService chatService) {
        ChatEndpoint.chatService = chatService;
    }

    /**
     * 载入团队服务服务类
     *
     * @param teamService 团队服务
     */
    @Resource
    public void setTeamService(TeamService teamService) {
        ChatEndpoint.teamService = teamService;
    }


    /**
     * 建立WebSocket连接后被调用
     *
     * @param session 会话
     * @param userId  用户id
     * @param teamId  团队id
     * @param config  配置
     */
    @OnOpen
    public void onOpen(Session session,
                       @PathParam(value = "userId") String userId,
                       @PathParam(value = "teamId") String teamId,
                       EndpointConfig config) {

        //  参数异常处理
        if (StrUtil.isBlank(userId) || UNDEFINED.equals(userId)) {
//            sendError(userId, "参数有误");
            return;
        }
        //2.通过路径传来的用户id，查找其用户信息，存入HttpSession中
        User user = userService.getById(Long.valueOf(userId));
        HttpSession userHttpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        userHttpSession.setAttribute(USER_LOGIN_STATE, user);
        this.httpSession = userHttpSession;
        this.session = session;
        //3.判断是否路径参数是否有队伍Id，如果有代表需建立聊天室连接
        if (!NAN.equals(teamId)) {
            //3.1如果此聊天室之前未有连接信息则将其信息加入在线聊天室记录中
            if (!ROOMS.containsKey(teamId)) {
                ConcurrentHashMap<String, ChatEndpoint> room = new ConcurrentHashMap<>(0);
                room.put(userId, this);
                ROOMS.put(String.valueOf(teamId), room);
            } else {
                //3.2如果聊天室已有连接，且聊天室连接信息中无当前用户信息，则添加用户信息至聊天室在线信息中
                if (!ROOMS.get(teamId).containsKey(userId)) {
                    ROOMS.get(teamId).put(userId, this);
                }
            }
        } else {
            //4.不是聊天室功能，则加入私聊功能所需信息
        }
            SESSIONS.add(session);
            ONLINE_USERS.put(user.getUserId().toString(), session);
        //5.给所有登录用户发送信息来同步在线用户信息
        sendAllUsers();
    }


    /**
     * 消息
     *
     * @param message 消息
     * @param userId  用户id
     */
    @OnMessage
    public void onMessage(String message, @PathParam("userId") String userId) {
        //1.心跳检测
        if (PING.equals(message)) {
            sendOneMessage(userId, "PONG");
            return;
        }
        //2.将信息转换成信息接收类
        MessageRequest messageRequest = JSONUtil.toBean(message, MessageRequest.class);
        //3.获取信息接收类中的具体参数
        Long toId = messageRequest.getToId();
        Long teamId = messageRequest.getTeamId();
        String text = messageRequest.getText();
        Integer chatType = messageRequest.getChatType();
        User fromUser = userService.getById(userId);
        Team team = teamService.getById(teamId);
        //4.根据聊天的类型调用不同的方法
        if (chatType == PRIVATE_CHAT) {
            // 私聊
            privateChat(fromUser, toId, text, chatType);
        } else if (chatType == TEAM_CHAT) {
            // 队伍内聊天
            teamChat(fromUser, text, team, chatType);
        } else {
            // 群聊
            hallChat(fromUser, text, chatType);
        }


    }

    /**
     * 大厅聊天
     *
     * @param user     用户
     * @param text     文本
     * @param chatType 聊天类型
     */
    private void hallChat(User user, String text, Integer chatType) {
        //1.封装消息返回类
        ChatMessageVO chatMessageVo = new ChatMessageVO();
        WebSocketVO fromWebSocketVO = new WebSocketVO();
        BeanUtils.copyProperties(user, fromWebSocketVO);
        chatMessageVo.setFromUser(fromWebSocketVO);
        chatMessageVo.setText(text);
        chatMessageVo.setChatType(chatType);
        chatMessageVo.setCreateTime(DateUtil.format(new Date(), "yyyy年MM月dd日 HH:mm:ss"));
        User loginUser = (User) this.httpSession.getAttribute(USER_LOGIN_STATE);
        if (Objects.equals(loginUser.getUserId(), user.getUserId())) {
            chatMessageVo.setIsMy(true);
        }
        String toJson = JSONUtil.toJsonStr(chatMessageVo);
        //发送信息
        sendAllMessage(toJson);
        //保存聊天记录，删除缓存
        saveChat(user.getUserId(), null, text, null, chatType);
        chatService.deleteKey(CACHE_CHAT_HALL, String.valueOf(user.getUserId()));
    }

    /**
     * 队伍聊天
     *
     * @param user     用户
     * @param text     文本
     * @param team     团队
     * @param chatType 聊天类型
     */
    private void teamChat(User user, String text, Team team, Integer chatType) {
        //1.封装消息返回类
        ChatMessageVO chatMessageVo = new ChatMessageVO();
        WebSocketVO fromWebSocketVO = new WebSocketVO();
        BeanUtils.copyProperties(user, fromWebSocketVO);
        chatMessageVo.setFromUser(fromWebSocketVO);
        chatMessageVo.setText(text);
        chatMessageVo.setTeamId(team.getId());
        chatMessageVo.setChatType(chatType);
        chatMessageVo.setCreateTime(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        if (Objects.equals(user.getUserId(), team.getUserId())) {
            chatMessageVo.setIsAdmin(true);
        }
        User loginUser = (User) this.httpSession.getAttribute(USER_LOGIN_STATE);
        if (Objects.equals(loginUser.getUserId(), user.getUserId())) {
            chatMessageVo.setIsMy(true);
        }
        String toJson = JSONUtil.toJsonStr(chatMessageVo);
        try {
            //3.在队伍聊天室中群发此信息
            broadcast(String.valueOf(team.getId()), toJson);
            //4.保存消息到数据库
            saveChat(user.getUserId(), null, text, team.getId(), chatType);
            //5.删除缓存消息方便消息同步
            chatService.deleteKey(CACHE_CHAT_TEAM, String.valueOf(team.getId()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 队伍内群发消息
     *
     * @param teamId 团队id
     * @param msg    消息
     */
    public static void broadcast(String teamId, String msg) {
        ConcurrentHashMap<String, ChatEndpoint> map = ROOMS.get(teamId);
        // keySet获取map集合key的集合  然后在遍历key即可
        for (String key : map.keySet()) {
            try {
                ChatEndpoint chatEndpoint = map.get(key);
                chatEndpoint.sendMessage(msg);
            } catch (Exception e) {
                log.error("exception message", e);
            }
        }
    }

    /**
     * 发送消息
     *
     * @param message 消息
     * @throws IOException ioexception
     */
    public void sendMessage(String message) throws IOException {
        try {
        this.session.getBasicRemote().sendText(message);
        }catch (Exception e) {
            log.error("exception message", e);
        }
    }

    /**
     * 私聊
     *
     * @param user     用户
     * @param toId     为id
     * @param text     文本
     * @param chatType 聊天类型
     */
    private void privateChat(User user, Long toId, String text, Integer chatType) {
        //1.获取消息返回包装对象
        ChatMessageVO chatMessageVo = chatService
                .chatResult(user.getUserId(), toId, text, chatType, DateUtil.date(System.currentTimeMillis()));
        User loginUser = (User) this.httpSession.getAttribute(USER_LOGIN_STATE);
        //2.判断此消息是否为登录用户发送
        if (Objects.equals(loginUser.getUserId(), user.getUserId())) {
            chatMessageVo.setIsMy(true);
        }
        //3.将消息类转换成JSON格式
        String toJson = JSONUtil.toJsonStr(chatMessageVo);
        //4.调用发送单条消息方法
        sendOneMessage(toId.toString(), toJson);
        //5.将聊天记录存入数据库
        saveChat(user.getUserId(), toId, text, null, chatType);
        //6.删除对应用户存储在Redis中的消息数据，包装数据一致性
        chatService.deleteKey(CACHE_CHAT_PRIVATE, user.getUserId() + "" + toId);
        chatService.deleteKey(CACHE_CHAT_PRIVATE, toId + "" + user.getUserId());
    }

    /**
     * 保存聊天到数据库中
     *
     * @param userId   用户id
     * @param toId     目标用户id
     * @param text     文本消息
     * @param teamId   团队id
     * @param chatType 聊天类型
     */
    private void saveChat(Long userId, Long toId, String text, Long teamId, Integer chatType) {
        //1.封装消息类
        Chat chat = new Chat();
        chat.setFromId(userId);
        chat.setText(String.valueOf(text));
        chat.setChatType(chatType);
        chat.setCreateTime(new Date());
        //2.有目标用户id才设置
        if (toId != null && toId > 0) {
            chat.setToId(toId);
        }
        //3.有队伍id才设置
        if (teamId != null && teamId > 0) {
            chat.setTeamId(teamId);
        }
        chatService.save(chat);
    }

    /**
     * 关闭
     *
     * @param userId  用户id
     * @param teamId  团队id
     * @param session 会话
     */
    @OnClose
    public void onClose(@PathParam("userId") String userId,
                        @PathParam(value = "teamId") String teamId,
                        Session session) {
        try {
            if (!"NaN".equals(teamId)) {
                ROOMS.get(teamId).remove(userId);
            } else {
                if (!ONLINE_USERS.isEmpty()) {
                    ONLINE_USERS.remove(userId);
                    SESSIONS.remove(session);
                }
                sendAllUsers();
            }
            log.info("用户：{} 退出了聊天界面", userId);
        } catch (Exception e) {
            log.error("exception message", e);
        }
        sendAllUsers();


    }

    /**
     * 发送一个消息
     *
     * @param userId  用户编号
     * @param message 消息
     */
    public void sendOneMessage(String userId, String message) {
        Session userSession = ONLINE_USERS.get(userId);
        if (userSession != null && userSession.isOpen()) {
            try {
                synchronized (userSession) {
                    log.info("向用户{}发送了信息{}", userId, message);
                    userSession.getBasicRemote().sendText(message);
                }
            } catch (Exception e) {
                log.error("exception message", e);
            }
        }
    }

    /**
     * 给所有用户发送信息
     */
    public void sendAllUsers() {
        HashMap<String, List<WebSocketVO>> stringListHashMap = new HashMap<>(0);
        List<WebSocketVO> webSocketVos = new ArrayList<>();
        stringListHashMap.put("users", webSocketVos);
        for (Serializable key : ONLINE_USERS.keySet()) {
            User user = userService.getById(key);
            WebSocketVO webSocketVO = new WebSocketVO();
            BeanUtils.copyProperties(user, webSocketVO);
            webSocketVos.add(webSocketVO);
        }
        sendAllMessage(JSONUtil.toJsonStr(stringListHashMap));
    }

    /**
     * 广播消息
     *
     * @param message 消息
     */
    public void sendAllMessage(String message) {
        for (Session userSession : SESSIONS) {
            try {
                if (userSession.isOpen() && userSession!= session) {
                    synchronized (userSession) {
                        userSession.getBasicRemote().sendText(message);
                    }
                }
            } catch (Exception e) {
                log.error("exception message", e);
            }
        }
    }
}
